<template>
  <div
    ref="panel"
    @mousemove="touch($event)"
    @mouseup="drawFinish"
    @mouseleave="drawFinish()"
    class="drawing-panel"
  >
    <!-- 画板 -->
    <div class="canvas">
      <canvas ref="canvasContainer"></canvas>
    </div>
    <!-- 图形绘制 -->
    <div class="shape-container" v-show="isDrawing">
      <div
        v-if="
          type === 'rect' &&
          shapeX1 !== undefined &&
          shapeY1 !== undefined &&
          shapeY2 !== undefined &&
          shapeX2 !== undefined
        "
        class="shape"
        :style="{
          background: fillColor,
          border: `${color} solid ${Number(width)}px`,
          left: Math.min(shapeX1, shapeX2) + 'px',
          top: Math.min(shapeY1, shapeY2) + 'px',
          width: Math.abs(shapeX2 - shapeX1) + 'px',
          height: Math.abs(shapeY2 - shapeY1) + 'px'
        }"
      ></div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const canvasContainer = ref<HTMLElement>()
const panel = ref<HTMLElement>()
let lastPoint: [number, number, number] | undefined
let level = 0
const mouseX = ref(0)
const mouseY = ref(0)
const shapeX1 = ref<number | undefined>(undefined)
const shapeY1 = ref<number | undefined>(undefined)
const shapeX2 = ref<number | undefined>(undefined)
const shapeY2 = ref<number | undefined>(undefined)
const disabled = ref(false)
const canvas = computed(() => {
  const c = canvasContainer.value as HTMLCanvasElement
  c.width = c.clientWidth * devicePixelRatio
  c.height = c.clientHeight * devicePixelRatio
  return c
})
const emit = defineEmits([
  'down', //画笔按下事件
  'draw', //画笔绘画事件
  'update' //画板数据更新事件
])
const props = defineProps({
  width: {
    type: [Number, String],
    default: 1
  },
  color: {
    type: String,
    default: '#000'
  },
  fillColor: {
    type: String,
    default: '#000'
  },

  type: {
    type: String,
    default: 'rect'
  }
})

type DataOptionPoint = [
  0, //画笔
  string, //颜色
  number, //宽度
  [
    // 点集
    number, //x
    number //y
  ][]
]
type DataOptionShape = [
  1, //形状
  number, //x1
  number, //y1
  number, //x2
  number, //y2
  string, //线条颜色
  string, //填充颜色
  number, //线条宽度
  number //圆角[binary: (0~15)]
]
type DataOption = DataOptionPoint | DataOptionShape
type DrawingPanelData = DataOption[]
const data = ref<DrawingPanelData>([])
let isDrawing = false

//绘画结束
const drawFinish = () => {
  if (!isDrawing) return
  isDrawing = false
  level++

  if (shapeX1.value && shapeY1.value && shapeX2.value && shapeY2.value) {
    if (props.type === 'rect') {
      data.value.push([
        1,
        shapeX1.value,
        shapeY1.value,
        shapeX2.value,
        shapeY2.value,
        props.color,
        props.fillColor,
        Number(props.width),
        0
      ])
      const w = Number(props.width) / 2
      rectShape(
        Math.min(shapeX1.value, shapeX2.value) + w,
        Math.min(shapeY1.value, shapeY2.value) + w,
        Math.max(shapeX1.value, shapeX2.value) - w,
        Math.max(shapeY1.value, shapeY2.value) - w
      )
    }
    shapeX1.value = shapeX2.value = shapeY1.value = shapeY2.value = undefined
  }
  emit('update', data.value)
}

const touch = (event: MouseEvent) => {
  if (!canvas.value || !panel.value) return
  let scrollTop = 0
  let scrollLeft = 0
  let p: HTMLElement = canvas.value
  while (p.parentElement) {
    scrollTop += p.scrollTop - p.offsetTop
    scrollLeft += p.scrollLeft - p.offsetLeft
    p = p.parentElement
  }

  mouseX.value = event.clientX + scrollLeft - panel.value.offsetLeft
  mouseY.value = event.clientY + scrollTop - panel.value.offsetTop

  if (event.buttons !== 1) {
    drawFinish()
    return
  }

  if (disabled.value) return

  const x = (event.clientX - canvas.value.offsetLeft + scrollLeft) * devicePixelRatio - 30
  const y = (event.clientY - canvas.value.offsetTop + scrollTop) * devicePixelRatio - 60

  if (lastPoint) {
    const distance = Math.sqrt(Math.pow(lastPoint[1] - x, 2) + Math.pow(lastPoint[2] - y, 2))
    if (distance < 5) {
      return
    }
  }

  const point: [number, number] = [x, y]
  if (props.type === 'pen') {
    if (level >= data.value.length) {
      data.value.push([0, props.color, Number(props.width), []])
    }
    ;(data.value[level][3] as unknown as [number, number][]).push(point)
    draw(x, y)
    emit('draw', point, data.value)
    emit('update', data.value)
  } else if (props.type === 'rect') {
    if (event.buttons === 1) {
      if (shapeX1.value === undefined || shapeY1.value === undefined) {
        shapeX1.value = mouseX.value
        shapeY1.value = mouseY.value
      }
      shapeX2.value = mouseX.value
      shapeY2.value = mouseY.value
    }
  }
  isDrawing = true
}

//图形
const rectShape = (x1: number, y1: number, x2: number, y2: number, width = props.width) => {
  if (!canvas.value) return
  var ctx = canvas.value.getContext('2d')
  if (ctx) {
    ctx.beginPath()
    ctx.strokeStyle = props.color
    ctx.fillStyle = props.fillColor
    ctx.imageSmoothingEnabled = true
    ctx.imageSmoothingQuality = 'high'
    ctx.lineWidth = Number(width) * 1.5
    ctx.lineJoin = 'miter'
    ctx.lineCap = 'square'
    ;[ctx.strokeRect, ctx.fillRect].forEach((r) => {
      r.call(
        ctx,
        x1 * devicePixelRatio,
        y1 * devicePixelRatio,
        x2 * devicePixelRatio - x1 * devicePixelRatio,
        y2 * devicePixelRatio - y1 * devicePixelRatio
      )
    })

    ctx.closePath()
    ctx.save()
  }
}

//绘画
const draw = (x: number, y: number, width = props.width) => {
  if (!canvas.value) return
  if (lastPoint && lastPoint[0] === level) {
    var ctx = canvas.value.getContext('2d')
    if (ctx) {
      ctx.beginPath()
      ctx.strokeStyle = props.color
      ctx.fillStyle = props.color
      ctx.imageSmoothingEnabled = true
      ctx.imageSmoothingQuality = 'high'
      ctx.lineWidth = Number(width)
      ctx.lineJoin = 'round'
      ctx.lineCap = 'round'
      ctx.moveTo(lastPoint[1], lastPoint[2])
      ctx.lineTo(x, y)
      ctx.stroke()
      ctx.closePath()
      ctx.save()
    }
  }
  lastPoint = [level, x, y]
}

defineExpose({
  clear() {
    data.value = []
    emit('update', data.value)
    level = 0
    lastPoint = undefined
    canvas.value.width = canvas.value.clientWidth * devicePixelRatio
    canvas.value.height = canvas.value.clientHeight * devicePixelRatio
  },
  setData(d: DrawingPanelData) {
    data.value = d
    if (d.length > 0) {
      const next = (l: number, i: number) => {
        level = l
        if (l >= d.length) {
          disabled.value = false
          return
        }
        let element = d[level]
        if (element[0] === 0) {
          element = element as DataOptionPoint
          const points = element[3] as [number, number][]
          if (i < points.length) {
            setTimeout(() => {
              const point = points[i]
              draw(point[0], point[1], d[level][2])
              next(level, i + 1)
            }, 10)
          } else {
            lastPoint = undefined
            next(level + 1, 0)
          }
        } else {
          element = element as unknown as DataOptionShape
          rectShape(element[1], element[2], element[3], element[4], element[7])
          next(level + 1, 0)
        }
      }
      disabled.value = true
      next(0, 0)
    }

    emit('update', d)
  }
})
</script>
<style lang="scss">
.drawing-panel {
  user-select: none;
  width: 500px;
  height: 500px;
  background: #ededed;
  position: relative;

  .canvas {
    width: 100%;
    height: 100%;
    position: absolute;

    canvas {
      width: 100%;
      height: 100%;
    }
  }

  .shape-container {
    overflow: hidden;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;

    .shape {
      box-sizing: border-box;
      position: absolute;
    }
  }
}
</style>
